package com.self.ry.utils;

import org.apache.commons.codec.binary.Base64;
import sun.security.rsa.RSAPublicKeyImpl;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.HashMap;
import java.util.Map;

/**   
* Copyright: Copyright (c) 2018
* 
* @ClassName: RSAUtil.java
* @Description: RSA加密解密
*
* @version: v1.0.0
* @author: 汝毅
* @date: 2018年10月29日 下午3:51:35 
*
* Modification History:
* Date         Author          Version            Description
*---------------------------------------------------------*
* 2018年10月29日     汝毅           v1.0.0               修改原因
*/
public final class RSAUtil {
	/**
	 * 1. RSA是一种非对称加密算法，常用来对传输数据进行加密，配合上数字摘要算法，也可以进行文字签名。
	 * 2. RSA加密中padding？
	 *  答：padding即填充方式，由于RSA加密算法中要加密的明文是要比模数小的，padding就是通过一些填充方式来限制明文的长度。
	 *  后面会详细介绍padding的几种模式以及分段加密。
	 * 3. 加密：公钥放在客户端，并使用公钥对数据进行加密，服务端拿到数据后用私钥进行解密；
	 *    加签：私钥放在客户端，并使用私钥对数据进行加签，服务端拿到数据后用公钥进行验签。
	 *    前者完全为了加密；后者主要是为了防恶意攻击，防止别人模拟我们的客户端对我们的服务器进行攻击，导致服务器瘫痪。
	 *    
	 * RSA加密中Padding
	 * RSA_PKCS1_PADDING 填充模式，最常用的模式
	 * 要求: 输入：必须 比 RSA 钥模长(modulus) 短至少11个字节, 也就是　RSA_size(rsa) – 11 如果输入的明文过长，必须切割，然后填充。
	 * 输出：和modulus一样长
	 * 根据这个要求，对于1024bit的密钥，block length = 1024/8 – 11 = 117 字节
	 * 
	 * RSA_PKCS1_OAEP_PADDING
	 * 输入：RSA_size(rsa) – 41
	 * 输出：和modulus一样长
	 * 
	 * RSA_NO_PADDING　　不填充
	 * 输入：可以和RSA钥模长一样长，如果输入的明文过长，必须切割，　然后填充
	 * 输出：和modulus一样长
	 */
	
	// 默认算法
    public static final String RSA_ALGORITHM = "RSA";
    // 密钥默认长度---bit
    public static final int LENGTH = 1024;

    /** *//**
     * RSA最大加密明文大小
     * keysize / 8 - 11
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    
    /** *//**
     * RSA最大解密密文大小
     * keysize / 8 
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

	public static final String PUBLIC_KEY = "public_key";
	public static final String PRIVATE_KEY = "private_key";

	/**
	 * 生成密钥对
	 * @return
	 */
	public static Map<String, String> genKeyPair() {
		return genKeyPair(LENGTH);
	}
	/*genKeyPair(256);
            BigInteger n = new BigInteger("65537", 10);
            byte[] bytes = n.toByteArray();
            String s = ByteHexUtil.Bytes2String(bytes, bytes.length);
            int len = s.length();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < len - 8; i += 8) {
                sb.append(s.substring(i, i+ 8));
                sb.append(" ");
            }
            System.out.println(sb.toString());

            String s1 = Integer.toHexString(65537);
            byte[] intToByteArray = ByteHexUtil.intToByteArray(65537);
            System.out.println(s1);*/

	/**
	 * 将数组反转
	 * @param src
	 * @return
	 */
	private static byte[] byteRersever(byte[] src) {
		byte[] dest = new byte[src.length];

		for (int m = 0, len = src.length; m < len; m++) {
			dest[m] = src[len - 1 - m];
		}

		return dest;
	}

	private static String bigIntToString(BigInteger num){
		byte[] bytes = num.toByteArray();
		System.out.println(ByteHexUtil.Bytes2String(bytes, bytes.length));
		bytes = byteRersever(bytes);

		String s = ByteHexUtil.Bytes2String(bytes, bytes.length);
		System.out.println(s);
		StringBuilder sb = new StringBuilder();
		int len = s.length();
		if (len < 8) {
			//sb.append("0x");
			sb.append(s);
			for (int i = 0; i < 8 - len; i++) {
				sb.append("0");
			}
			return sb.toString();
		}

		for (int i = 0; i < len - 8; i += 8) {
			//sb.append("0x");
			sb.append(s.substring(i, i+ 8));
			/*if (i != len - 9){
				sb.append(",");
			}*/
		}
		return sb.toString();
	}


	public static void main(String[] args) throws InvalidKeySpecException, NoSuchAlgorithmException {
		String n = "108014686341751224364345912112222182159929732066101398852195674030137191643482550767669192137600821654670858345606428154153555443797532991330190065509559091072092574143513152967372706060825614663990439071053402937735067539732094396292333448500658773232533051815960388216803603139737766220273573711266477986781";
		String e = "65537";

		BigInteger bigN = new BigInteger(n);
		BigInteger bigE = new BigInteger(e);
		System.out.println(bigIntToString(bigN));
		System.out.println(bigIntToString(bigE));

		/*String d = "79 f7 24 35 39 74 e1 70 34 62 d1 b2 11 2a f1 be 8b 92 35 fb 2a 5e 7f af f3 06 6b b7 b6 32 9f 6b 11 4f ac 67 fe 0c 89 3a 9b 72 bc c2 2a 32 ac f8 44 cb 1c bf bf 97 36 e7 fe c9 92 8f 8e dd 66 bb 17 d1 ca ea f0 3b 5a 06 2f f6 80 38 ab 46 97 69 20 ab 3d 2c 74 e8 ba 66 47 1e cf 3a 29 43 ff d7 00 94 50 28 cb 16 26 17 c3 a6 08 46 ef b2 df 40 e2 ff 58 97 7a 6b 84 f9 1d e1 0f f0 f9 37 56 88 3a 8b 38 41 2c 72 e4 ee ee 3c 6a 98 c1 36 15 3d 3d 45 a2 32 19 9e a7 8d c5 13 fb 12 ea e4 6d fe e3 95 92 44 39 e9 38 5d 01 b0 69 85 23 4b ad f9 f9 91 6f 35 68 f6 df 5e 69 b9 7b 17 72 7c a8 40 7c 91 48 47 a5 ec 0f a0 cc 08 bc 0c d7 6d 47 13 02 c7 85 0b d9 0a 70 e3 11 84 c6 82 64 b1 da 25 2b 17 fe 3c 1f 77 90 da af 77 8f 11 26 cc ed bd 05 28 1f eb 3d 6f 96 dd 80 cc a9 b4 a1 18 90 8e";
		String n = "8f 83 48 c7 f0 a3 59 d1 b8 ec 7b cc 2d 47 6d a5 03 53 0e 58 20 6b b2 f4 d7 0d aa f8 90 9a 7d a2 48 00 28 d4 06 fe 70 5b d8 a0 ea 3a 30 8b 38 c8 5e 67 b8 98 a5 ef 7d 15 61 5c 9f d1 46 5d d7 1c c9 d9 72 7d 1f f8 15 8c bc 03 41 94 e3 aa 5f da 3e 76 65 65 80 05 1e a2 d5 a9 3f a6 d6 a1 f3 4a 57 f5 f7 1f df 61 d2 a1 fb 14 41 bb 0c 92 39 9f 54 d2 b6 99 78 f2 20 a8 4c 7b e2 c7 e4 26 3b c2 1a 29 04 7e 65 ee 4a 6d 9d e8 38 4c c8 68 ba 46 c5 47 42 88 26 be 6d 4f 92 7b 6e 34 7f 3c 4e 35 fc 8b 96 a7 9a 8e 53 ab 93 4a 8b 3a 1a 8f fa f0 59 c7 38 1f f0 9a d2 54 3b ee 1e f9 a5 e8 3b b4 c8 55 06 b1 d1 b2 68 90 e3 e9 bb 1f e1 9f a4 58 66 79 96 bd e7 55 eb 8b ee 74 b0 67 f0 9c 31 b6 49 43 f8 78 3e 87 c5 8e d1 2a 36 be 6e 71 d3 c8 cf d2 bc 96 55 09 0d ef eb ec b7 a0 50 fc 2c be";
		String e = "01 00 01 00";*/

		//d = d.replaceAll("\\s+", "");
		//n = n.replaceAll("\\s+", "");
		//e = e.replaceAll("\\s+", "");
		/*String d = "5210699566766742921892159246816348112307320654775812557504966605952383582909302356575301395540628239405158892986450820235760948027613997634122124517720970123627460899828183035748170455195302063364786998043008132045518462307125119279992471652373379484540408933598257998778223307504766767280136610374230945867193486970328865902445384937701661622882444650993308605057445527504832210936523713640483351749255948604224145263653040596963246875895388037601887174129121245509327927540182091870421900152289263855974740817472053530967283829262313395213719937344787431276960144617481028800682096090260602892593346959194560453573";
		String n = "25093219010007497308549227757998604323336385755899950590139098865037942749145929057452827361271669698427209814802926549033071147834942946796051265671091132264837453522818695834655584328175068802023517193823545003296872912353740829028794666373840412321134894575738778342709120501428459080552451541927839922059121876363616191193605126662714869239092877989850432558288357392427998920469117161669056801315781409493696246136254588830506950993600633062497636913670605445371712246563258619298557862988469328035594677465017077353557081452305715524084987047208161812811172618179949890464887602730431475979560481602116469735089";
		String e = "65537";

		String sdd = new BigInteger(d, 10).toString();
		String snn = new BigInteger(n, 10).toString();
		String see = new BigInteger(e, 10).toString();

		System.out.println("e="+see+", \n n="+snn+"\n, d="+sdd);

		PublicKey publicKey = getPublicKey(snn, see);
		String publicKeyString = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
		System.out.println("publicKeyString="+publicKeyString);

		PrivateKey privateKey = getPrivateKey(snn, sdd);
		String privateKeyString = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
		System.out.println("privateKeyString="+privateKeyString);*/

		/*BigInteger n = new BigInteger("123560900669517648520966301424865781567696905359209011579444672096221122428525430522415534938841976288870500837532087644716411875302869995823782205709837343539087355816796765360011010193954829578665572181223511692809585672017816962135219795953834699558497087647692448463381526227691727702968265103722948480653", 10);
		byte[] bytes = n.toByteArray();
		String s = ByteHexUtil.Bytes2String(bytes, bytes.length);
		int len = s.length();
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < len - 8; i += 8) {
			sb.append(s.substring(i, i+ 8));
			sb.append(" ");
		}
		System.out.println(sb.toString());

		String s1 = Integer.toHexString(65537);
		byte[] intToByteArray = ByteHexUtil.intToByteArray(65537);
		System.out.println(s1);*/
		String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZ0Wmz4spUiFpmYeO70kPHGIwhTaWQ8W2QDStzrfQ7ZqV_AEbktLhSq3o9aPZWVfVYop0oSVKmmUga-3_YJ-FBbn31vEGUx25OswigbcJiGcaq0FRMwAX6QcSewgAbQwVw9lH6qJB7rTxdptmlkSICWTzNPTY35pWjFN9KqzNT3QIDAQAB";
		RSAPublicKey rsaPublicKey = getPublicKey(publicKey);

		BigInteger modulus = rsaPublicKey.getModulus();
		System.out.println(modulus);
	}
	/**
	 * 生成密钥对
	 * @param keySize 指定 密钥的bit位数
	 * @return
	 */
	public static Map<String, String> genKeyPair(int keySize) {
		KeyPairGenerator keyPairGenerator = null;
		Map<String, String> keyPairMap = new HashMap<>();
		try {
			keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
			// 设置算法的度量 模数长度 以位数指定
			keyPairGenerator.initialize(keySize);

			KeyPair keyPair = keyPairGenerator.genKeyPair();
			// 公钥
			RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
			String publicKeyString = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
			BigInteger modulus = publicKey.getModulus();

			System.out.println("publicKeyString="+publicKeyString+"\n"
					+"modulus="+ modulus +"\n"
			+ "public exponent="+publicKey.getPublicExponent());

			// 私钥
			RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
			String privateKeyString = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
			System.out.println("privateKeyString="+privateKeyString+"\n"
			+"modulus="+privateKey.getModulus()+"\n"
			+"private exponent="+privateKey.getPrivateExponent() +"\n"
			+"private alg="+privateKey.getAlgorithm()+"\n"
			+"format="+privateKey.getFormat());


			keyPairMap.put(PUBLIC_KEY, publicKeyString);
			keyPairMap.put(PRIVATE_KEY, privateKeyString);
			keyPairMap.put("n", publicKey.getModulus().toString());
			keyPairMap.put("e", publicKey.getPublicExponent().toString());
			return  keyPairMap;
		} catch (Exception e) {
			throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
		}
	}
	
	/**
	 * 获得 公钥
	 * @param rsaPublicKey：公钥
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static PublicKey getPublicKey(RSAPublicKey rsaPublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
		KeyFactory factory = KeyFactory.getInstance(RSA_ALGORITHM);
		PublicKey publicKey = factory.generatePublic(keySpec);
		return publicKey;
	}
	
	/**
	 * 获得 私钥
	 * @param rsaPrivateKey
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	private static PrivateKey getPrivateKey(RSAPrivateKey rsaPrivateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
		KeyFactory factory = KeyFactory.getInstance(RSA_ALGORITHM);
		PrivateKey privateKey = factory.generatePrivate(keySpec);
		
		return privateKey;
	}
	
	/**
	 * 利用 公钥加密
	 * @param data 要加密的数据
	 * @param publicKey 公钥
	 * @return
	 */
	private static byte[] publicKeyEncrypt(byte[] data, PublicKey publicKey) {
		try {
			// 加密
			return dataOperation(data, publicKey, Cipher.ENCRYPT_MODE);
		} catch (Exception e) {
			throw new RuntimeException("加密字符串[" + ByteHexUtil.Bytes2String(data, data.length) + "]时遇到异常", e);
		}
	}
	
	/**
	 * 利用 私钥解密
	 * @param data 要解密的数据
	 * @param privateKey 私钥
	 * @return
	 */
	private static byte[] privateKeyDecrypt(byte[] data, PrivateKey privateKey) {
		try {
			// 解密
			return dataOperation(data, privateKey, Cipher.DECRYPT_MODE);
		} catch (Exception e) {
			throw new RuntimeException("解密字符串[" + ByteHexUtil.Bytes2String(data, data.length) + "]时遇到异常", e);
		}
	}
	
	/**
	 * 利用 私钥加密
	 * @param data 要加密的数据
	 * @param privateKey 私钥
	 * @return
	 */
	private static byte[] privateKeyEncrypt(byte[] data, PrivateKey privateKey) {
		try {
			// 加密
			return dataOperation(data, privateKey, Cipher.ENCRYPT_MODE);
		} catch (Exception e) {
			throw new RuntimeException("加密字符串[" + ByteHexUtil.Bytes2String(data, data.length) + "]时遇到异常", e);
		}
	}
	
	/**
	 * 利用 公钥解密
	 * @param data 要解密的数据
	 * @param publicKey 公钥
	 * @return
	 */
	private static byte[] publicKeyDecrypt(byte[] data, PublicKey publicKey) {
		try {
			return dataOperation(data, publicKey, Cipher.DECRYPT_MODE);
		} catch (Exception e) {
			throw new RuntimeException("解密字符串[" + ByteHexUtil.Bytes2String(data, data.length) + "]时遇到异常", e);
		}
	}
	
	private static byte[] dataOperation(byte[] data, Key key, int cipherMode) throws Exception {
		int segmentSize = 0;
		if (Cipher.DECRYPT_MODE == cipherMode) {
			segmentSize = MAX_DECRYPT_BLOCK;
		} else if (Cipher.ENCRYPT_MODE == cipherMode) {
			segmentSize = MAX_ENCRYPT_BLOCK;
		}
		
		try {
			Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
			cipher.init(cipherMode, key);
			
			// 数据处理
			byte[] resultBytes = null;
			if (segmentSize > 0) {
				resultBytes = cipherDoFinal(cipher, data, segmentSize); // 分段加密
			}
			else {
				resultBytes = cipher.doFinal(data);
			}
			
			return resultBytes;
		} catch (Exception e) {
			throw e;
		}
	}
	
	
	private static byte[] cipherDoFinal(Cipher cipher, byte[] srcBytes, int segmentSize)
			throws IllegalBlockSizeException, BadPaddingException, IOException {
		if (segmentSize <= 0) {
			throw new RuntimeException("分段大小必须大于0");
		}
		
		int inputLen = srcBytes.length;
		byte[] data = null;
		try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
			int offSet = 0;
			byte[] cache;
			int i = 0;
			// 对数据分段解密
			while (inputLen - offSet > 0) {
				if (inputLen - offSet > segmentSize) {
					cache = cipher.doFinal(srcBytes, offSet, segmentSize);
				} else {
					cache = cipher.doFinal(srcBytes, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * segmentSize;
			}
			data = out.toByteArray();
		} catch (Exception e) {
			throw e;
		}
		
		return data;
	}

	/**
	 * 根据字符串密码获取 公钥
	 * @param publicKeyStr
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static RSAPublicKey getPublicKey(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
		RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);

		return publicKey;
	}

	/**
	 * 根据字符串密码获取 密钥
	 * @param privateKeyStr
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static RSAPrivateKey getPrivateKey(String privateKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
		RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
		return privateKey;
	}

	/**
	 * 私钥解密
	 * @param encodedData 密文
	 * @return 明文
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	/*public static byte[] privateKeyDecrypt(byte[] encodedData) throws NoSuchAlgorithmException, InvalidKeySpecException {
		PrivateKey rsaPrivateKey = getPrivateKey(privateKey);
        byte[] decodedData = privateKeyDecrypt(encodedData, rsaPrivateKey);
        return decodedData;
	}*/
	
	/**
	 * 公钥加密
	 * @param source 明文
	 * @return 密文
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	/*public static byte[] publicKeyEncrypt(byte[] source) throws NoSuchAlgorithmException, InvalidKeySpecException {
		PublicKey rsaPublicKey = getPublicKey(publicKey);
        byte[] encodedData = publicKeyEncrypt(source, rsaPublicKey);
        return encodedData;
	}*/


	/**
	 * 根据 n 和 e 作成公钥
	 * @param modulus  n   模数
	 * @param publicExponent  e  指数
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static RSAPublicKey getPublicKey(String modulus, String publicExponent)
			throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
		BigInteger bigIntModulus = new BigInteger(modulus);
		BigInteger bigIntPrivateExponent = new BigInteger(publicExponent);

		RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigIntModulus, bigIntPrivateExponent);
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
		RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
		System.out.println("publicKey="+publicKey.getModulus().toString(2).length());

		RSAPublicKeyImpl keyImpl = new RSAPublicKeyImpl(bigIntModulus, bigIntPrivateExponent);
		return publicKey;
	}

	/**
	 * 根据 n 和 d 作成密钥
	 * @param modulus  n   模数
	 * @param privateExponent   d 密钥
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static RSAPrivateKey getPrivateKey(String modulus, String privateExponent)
			throws NoSuchAlgorithmException, InvalidKeySpecException {
		BigInteger bigIntModulus = new BigInteger(modulus);
		BigInteger bigIntPrivateExponent = new BigInteger(privateExponent);

		RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(bigIntModulus, bigIntPrivateExponent);
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);

		RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
		System.out.println("privateKey="+privateKey.getModulus().toString(2).length()+"\n"
		+ "format="+privateKey.getFormat());
		return privateKey;
	}
}
